#####################################################################
# Product:      KnowledgeEngine (Plone Edition)
#
# File:         RandomAssessmentCreatorAccessory.py
#
# Copyright:    Copyright (c) 2005, Faulkner Technologies
#
# Author:       Brian Oliver
#
# Description:  This file defines the RandomAssessmentCreatorAccessory class,
#               that provides a mechanism to create a number of 
#               random assessments for a Model
#
# See Also:     Accessory, Model, Assessment, AssessmentStorageService
#####################################################################

#####################################################################
## KnowledgeEngine Library Imports
from Products.KnowledgeEngine.Common import *
from Products.KnowledgeEngine.Core import *
from Products.KnowledgeEngine.Accessory import *
from Products.KnowledgeEngine.PropertyDefinition import *
from Products.KnowledgeEngine.PropertyTypes import *
from Products.KnowledgeEngine.PropertyFormats import *
from Products.KnowledgeEngine.PropertyStructures import *
from Products.KnowledgeEngine.WizardStepDefinition import *

#####################################################################
## Zope Library Imports
from Globals import InitializeClass
from AccessControl import ClassSecurityInfo
from AccessControl.Role import RoleManager
 
#####################################################################
## Python Library Imports
from random import randint
from sets import Set

#####################################################################
## RandomAssessmentCreatorAccessory Class Definition
class RandomAssessmentCreatorAccessory(Accessory):
    """
        An interface specifying the standard features of an Accessory
    """

    #---------------------------------------------------------------#

    #define security
    security = ClassSecurityInfo()
    security.declareObjectPublic()
    security.setDefaultAccess("allow")
        
    #---------------------------------------------------------------#

    def getIdentity(self):
        """
            ABSTRACT: Return the unique identity of the accessory
            in the Knowledge Engine
        """
        
        #typically this will be the same as the class name
        return "RandomAssessmentCreatorAccessory"
        
    #---------------------------------------------------------------#
        
    def getTitle(self, genericObject):
        """
            Returns the title of the Accessory (when
            applied to the specified genericObject)
            
            RETURN TYPE: String
        """

        return "Random Assessment Creator"
        
    #---------------------------------------------------------------#
        
    def getPurpose(self, genericObject):
        """
            ABSTRACT: Returns the purpose of the Accessory (when
            applied to the specified genericObject)
            
            RETURN TYPE: String
        """

        return "Creates a number of randomly generated assessments for this Model.  Useful for testing."
        
    #---------------------------------------------------------------#
        
    def getIconName(self, genericObject):
        """
            ABSTRACT: Returns the name of the Icon of the Accessory (when
            applied to the specified genericObject).
            
            The IconName represents the image file name that will be found using
            acquisition.
            
            RETURN TYPE: String
        """
        
        return "random.gif"
        
    #---------------------------------------------------------------#
        
    def getExecuteActionDisplayName(self, genericObject):
        """
            ABSTRACT: Returns the display name for the execute action of the Accessory (when
            applied to the specified genericObject)
            
            RETURN TYPE: String
        """

        #return different action names based on the locked status of the model
        return "Generate"
        
    #---------------------------------------------------------------#
        
    def isAvailableForGenericObject(self, genericObject):
        """
            Returns if the accessory is available for the 
            specified genericObject.
            
            This method is called to determine if an Accessory
            should be displayed for the specified genericObject 
            when rendering it's property editor etc.

            RETURN TYPE: Boolean
        """

        return genericObject.isInstanceOf("Model")
        
    #---------------------------------------------------------------#
        
    def getWizardStepDefinitions(self, context=None):
        """
            ABSTRACT: This method is used by this class to
            determine the actual wizard step definitions
            for the wizard
            
            RETURN: [WizardStepDefinition]
        """

        wizardStep1Definition = WizardStepDefinition("Generation Options", "Please choose the Assessment Generation Options")
        wizardStep1Definition.addPropertyDefinition(PropertyDefinition(identity="nrAssessments", displayName="How Many Assessments?", description="The number of assessments containing randomly generated content to create.", defaultValue=0, propertyType=pt_INTEGER, propertyFormat=pf_NATIVE, propertyStructure=ps_ATOMIC, isForConstructor=false, hasEnumeratedValues=false))        
        wizardStep1Definition.addPropertyDefinition(PropertyDefinition(identity="repositoryIdentities", displayName="Repositories...", description="The repositories in which the randomly created assessments will be placed.", defaultValue=[], propertyType=pt_STRING, propertyFormat=pf_NATIVE, propertyStructure=ps_LIST, hasEnumeratedValues=true, enumeratedValuesMapping=[('(Anonymous User)', 'anonymous'), ('(Chosen User Repository)', 'user')] + [(repository.getTitleOrId(),repository.getIdentity()) for repository in context.getRepositories()]))
        wizardStep1Definition.addPropertyDefinition(PropertyDefinition(identity="dateFrom", displayName="Create From...", description="The date from which assessments will be randomly created (inclusive)", defaultValue=None, propertyType=pt_DATE, propertyFormat=pf_NATIVE, propertyStructure=ps_ATOMIC, isForConstructor=false, hasEnumeratedValues=false))        
        wizardStep1Definition.addPropertyDefinition(PropertyDefinition(identity="dateTo", displayName="Create To...", description="The date to which assessments will be randomly created (inclusive)", defaultValue=None, propertyType=pt_DATE, propertyFormat=pf_NATIVE, propertyStructure=ps_ATOMIC, isForConstructor=false, hasEnumeratedValues=false))        
        wizardStep1Definition.addPropertyDefinition(PropertyDefinition(identity="userIdentities", displayName="Users...", description="The users for which the randomly created assessments will be created.", defaultValue=[], propertyType=pt_STRING, propertyFormat=pf_NATIVE, propertyStructure=ps_LIST, hasEnumeratedValues=true, enumeratedValuesMapping=[('(Anonymous User)', 'anonymous')] + [(user.getFullname(),user.getIdentity()) for user in context.getUserProviderService().getUsers()]))

        return [wizardStep1Definition]
        
    #---------------------------------------------------------------#
        
    def generateRandomAssessmentForComponent(self, component, assessment):
        """
            Generates random content for the specified component in
            the provided assessment
            (does not save the assessment)
        """
        
        #generate component content based on the type of the component
        if component.isInstanceOf("CheckBox"):
            #create default state
            state = component.getDefaultAssessmentState(assessment)
        
            #make it random
            state.setValue(randint(0,1) == 1)

            #add to the assessment
            assessment.setComponentState(component.getIdentity(), state)

        elif component.isInstanceOf("Selection") and component.containsChildren():
            #how many selections to make (single or multiple selection)
            if component.isMultipleSelectionPermitted():
                nrSelections = randint(1, len(component.getChildren()))              
            else:
                nrSelections = 1
            
            #create a set of selected components
            optionIdentitiesToSelect = Set()
            while nrSelections > 0:
                #randomly chose one of the options
                optionIdentitiesToSelect.add(component.getChildren()[randint(0, len(component.getChildren())-1)].getIdentity())
                
                #one less option to choose
                nrSelections = nrSelections -1
                
            #select/unselect the options
            for option in component.getChildren():
                #create default state for the child
                state = option.getDefaultAssessmentState(assessment)
                state.setValue(option.getIdentity() in optionIdentitiesToSelect)

                #add to the assessment
                assessment.setComponentState(option.getIdentity(), state)
            
        elif component.isInstanceOf("MultipleChoice"):
            #create default state
            state = component.getDefaultAssessmentState(assessment)
        
            #get the possible answers
            answers = component.getAnswerList()
        
            #make it random
            state.setValue(answers[randint(0, len(answers)-1)].getAnswerID())

            #add to the assessment
            assessment.setComponentState(component.getIdentity(), state)

        elif component.isContainer():
            #add random assessment content for the components in the model
            for child in component.getChildren():            
                self.generateRandomAssessmentForComponent(child, assessment)

    #---------------------------------------------------------------#

    def getWizardStepFinishButtonTitle(self, wizardState, context=None):
        """
            Return the title of the finished button for the
            wizard step using the provided wizard state.
            
            PARAMS:
            wizardState - the current properties of the wizard in action
            context - usually a GenericObject instance that may 
                      be used to dynamically determine wizard
                      step definitions
            
            By default returns 'Finish...'.  Override this
            method if you'd like something else.

            RETURN: String
        """            
        
        return "Generate..."
        
    #---------------------------------------------------------------#
        
    def executeAccessoryWith(self, genericObject, wizardState):
        """
            Executes the accessory for the specified
            genericObject, using the specified wizardState.            
            
            Returns a pair indicating the success of the accessory on
            the component and a corresponding message.
            
            RETURN TYPE: (Boolean, String)
        """
 
        #get the model
        model = genericObject

        #get the assessment storage service
        assessmentStorageService = model.getAssessmentStorageService()        
        
        #determine the total number of assessments
        totalNrAssessments = assessmentStorageService.getNrAssessmentsForModel(model)
        
        #get the number of assessments to generate
        nrAssessments = wizardState.getPropertyValue("nrAssessments")
        
        #get the users that may own the assessments (set to 'anonymous' if none have been selected)
        userIdentities = wizardState.getPropertyValue("userIdentities")
        
        #the starting date and ending dates
        dateFrom = wizardState.getPropertyValue("dateFrom")
        dateTo = wizardState.getPropertyValue("dateTo")
        if dateTo.lessThan(dateFrom):
            tmp = dateFrom
            dateFrom = dateTo
            dateTo = dateFrom

        #determine the difference in days between the specified dates
        secondsDifference = int(dateTo.timeTime() - dateFrom.timeTime())
        minutesDifference = int(secondsDifference / 60)
        hoursDifference = int(minutesDifference / 60)
        daysDifference = int(hoursDifference / 24)
        
        #generate the assessments
        assessmentNr = 1
        while assessmentNr <= nrAssessments:
            #determine a random creation time
            creationTime = DateTime(dateFrom.timeTime() + randint(0, (daysDifference + 1) * 24 * 60 * 60)-1)   
        
            #choose a user that created the assessment
            if len(userIdentities) == 0:
                userIdentity = 'anonymous'
            else:         
                userIdentity = userIdentities[randint(0,len(userIdentities)-1)]
        
            #create a assessment
            assessment = assessmentStorageService.createAssessment(model)
            assessment.setName("Assessment %d" % (assessmentNr + totalNrAssessments))
            assessment.setCreatedBy(userIdentity)
            assessment.setInstantCreated(creationTime)
            assessment.setInstantModified(creationTime)

            #add random assessment content for the components in the model
            for component in model.getSections():            
                self.generateRandomAssessmentForComponent(component, assessment)
            
            #save the assessment
            assessmentStorageService.saveAssessment(assessment)

            #add the assessment to the appropriate repositories
            for repositoryIdentity in wizardState.getPropertyValue("repositoryIdentities"):
                #place in the 'user' repository?
                if repositoryIdentity == 'user':
                    assessmentStorageService.addAssessmentToRepository(assessment, userIdentity)
                else:
                    assessmentStorageService.addAssessmentToRepository(assessment, repositoryIdentity)
        
            #next assessment
            assessmentNr = assessmentNr + 1
 
        #always successful
        return (true, "Successfully Generated %d Assessments" % nrAssessments)
        
    #---------------------------------------------------------------#

#####################################################################
## Class Initialisation

# Register Accessory Information with the Knowledge Engine
registerAccessory(RandomAssessmentCreatorAccessory())

#####################################################################
